package com.bytz.modules.cms.domain.impl.pay;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.bytz.common.exception.BytzBootException;
import com.bytz.modules.cms.common.service.CmsConfigService;
import com.bytz.modules.cms.domain.PayProcessor;
import com.bytz.modules.cms.domain.model.pay.PayInBase;
import com.bytz.modules.cms.domain.model.pay.PayOut;
import com.bytz.modules.cms.domain.model.pay.PrepareContextBase;
import com.bytz.modules.cms.order.constant.OrderPayConstants;
import com.bytz.modules.cms.order.entity.*;
import com.bytz.modules.cms.order.model.OfflinePayIn;
import com.bytz.modules.cms.order.model.PayNotifyResult;
import com.bytz.modules.cms.order.model.PayResponseStatus;
import com.bytz.modules.cms.order.service.*;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static cn.hutool.core.date.DatePattern.PURE_DATE_PATTERN;
import static com.bytz.modules.cms.order.model.PayResponseStatus.Paying;
import static com.bytz.modules.cms.order.model.PayResponseStatus.Succeed;

public abstract class AbstractPayProcessor<PIN extends PayInBase, PC extends PrepareContextBase> implements PayProcessor<PIN> {

    protected final IOrderService orderService;

    protected final IOrderItemService orderItemService;

    protected final IOrderPayService orderPayService;

    protected final IOrderPayDetailService orderPayDetailService;

    protected final IOrderOfflinePayService orderOfflinePayService;

    protected final IOrderEmailService orderEmailService;
    protected final CmsConfigService configService;

    public AbstractPayProcessor(IOrderService orderService,
                                IOrderItemService orderItemService,
                                IOrderPayService orderPayService,
                                IOrderPayDetailService orderPayDetailService,
                                IOrderOfflinePayService orderOfflinePayService,
                                IOrderEmailService orderEmailService,
                                CmsConfigService configService) {
        this.orderService = orderService;
        this.orderItemService = orderItemService;
        this.orderPayService = orderPayService;
        this.orderPayDetailService = orderPayDetailService;
        this.orderOfflinePayService = orderOfflinePayService;
        this.orderEmailService = orderEmailService;
        this.configService = configService;
    }

    /**
     * 计算支付价格
     */
    public abstract void calculatePayAmount(PIN payIn, PC context);

    /**
     * 获取支付类型
     */
    protected abstract String getPayType();

    /**
     * 获取可以操作支付的订单状态
     * @return
     */
    protected abstract List<String> getCanPayOrderStatus();

    /**
     * 计算支付的商品信息
     */
    protected abstract String calculateCommodityMessage(PIN payIn, Order order, List<OrderItem> orderItems);

    /**
     * 创建准备上下文
     */
    protected abstract PC createPrepareContext();

    /**
     * 设置订单支付主数据自定义属性
     * @param orderPay 支付主数据
     * @param payIn 支付入参
     */
    protected abstract void setOrderPayCustomProperties(OrderPay orderPay, PIN payIn);

    @Override
    public PayOut prepare(PIN payIn) {
        PC context = createPrepareContext();

        String orderId = payIn.getOrderId();

        // 检查订单
        Order order = checkOrder(orderId);
        context.setOrder(order);

        // 检查线下付凭证
        List<OrderOfflinePay> offlinePays = checkOfflinePay(payIn.getOfflinePays());
        context.setOfflinePays(offlinePays);


        List<OrderItem> orderItems = orderItemService.getByOrderId(orderId);
        context.setOrderItems(orderItems);

        // 计算支付金额
        calculatePayAmount(payIn, context);
        BigDecimal payAmount = context.getPayAmount();

        // 创建支付主数据
        OrderPay orderPay = new OrderPay();
        orderPay.setOrderId(order.getId());
        orderPay.setPayType(getPayType());
        orderPay.setTotalPayAmount(payAmount);
        orderPay.setStatus(OrderPayConstants.PayStatus.PAYING);
        // 设置支付主数据其它属性
        setOrderPayCustomProperties(orderPay, payIn);

        context.setOrderPay(orderPay);

        // 创建支付明细数据
        List<OrderPayDetail> orderPayDetails = new LinkedList<>();
        context.setOrderPayDetails(orderPayDetails);

        // 先用线下付
        BigDecimal remainAmount = payByOffline(context);

        // 再用线上付
        OrderPayDetail payByOnline = payByOnline(payIn, context, remainAmount);

        // 如果没有线上付款
        if (payByOnline == null) {
            // 直接结算
            prepareNoOnlinePay(context);
        } else {
            // 有在线付
            prepareHasOnlinePay(context);
        }

        // 返回结果
        PayOut result = new PayOut();
        result.setGoOnline(payByOnline != null);
        result.setPayDetailId(payByOnline != null ? payByOnline.getId() : null);

        return result;
    }

    /**
     * 处理没有线上的情况
     */
    protected void prepareNoOnlinePay(PC context) {
        OrderPay orderPay = context.getOrderPay();
        List<OrderPayDetail> orderPayDetails = context.getOrderPayDetails();
        List<OrderOfflinePay> offlinePays = context.getOfflinePays();

        if (context.getPayAmount().compareTo(BigDecimal.ZERO) == 0) {
            // 首付比例100%，尾款为0的情况
            OrderPayDetail orderPayDetail = new OrderPayDetail();
            orderPayDetail.setPayAmount(BigDecimal.ZERO);
            orderPayDetail.setPayWay(OrderPayConstants.PayWay.OFFLINE);
            orderPayDetail.setStatus(OrderPayConstants.PayStatus.DONE);
            orderPayDetail.setTranDate(DateUtil.format(LocalDateTime.now(), PURE_DATE_PATTERN));
            orderPayDetails.add(orderPayDetail);
        } else {
            // 处理汇票
            orderOfflinePayService.submitPay(orderPayDetails, offlinePays);
        }

        // 处理支付和明细
        orderPay.setStatus(OrderPayConstants.PayStatus.DONE);
        orderPay.setCompleteDateTime(LocalDateTime.now());
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        for (OrderPayDetail orderPayDetail : orderPayDetails) {
            orderPayDetail.setCompleteDate(LocalDate.now().format(formatter));
            orderPayDetail.setCompleteDateTime(LocalDateTime.now());
            orderPayDetail.setStatus(OrderPayConstants.PayStatus.DONE);
        }

        // 汇票已经处理完，不需要再做保存
        orderPayService.save(orderPay);
        orderPayDetailService.saveOrderPayDetails(orderPay.getOrderId(), orderPay.getId(), orderPayDetails);
    }

    /**
     * 准备有在线付
     */
    protected void prepareHasOnlinePay(PC context) {
        OrderPay orderPay = context.getOrderPay();
        List<OrderPayDetail> orderPayDetails = context.getOrderPayDetails();
        List<OrderOfflinePay> offlinePays = context.getOfflinePays();

        orderPayService.save(orderPay);
        orderPayDetailService.saveOrderPayDetails(orderPay.getOrderId(), orderPay.getId(), orderPayDetails);
        if (offlinePays != null) {
            orderOfflinePayService.lockOfflinePays(offlinePays);
        }
    }

    /**
     * 支付成功
     */
    abstract void processOnlinePaySucceed(PayNotifyResult payNotifyResult, OrderPay orderPay, OrderPayDetail orderPayDetail);

    /**
     * 支付失败
     */
    abstract void processOnlinePayFailed(PayNotifyResult payNotifyResult, OrderPay orderPay, OrderPayDetail orderPayDetail);

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void notify(PayNotifyResult payNotifyResult, OrderPay orderPay, OrderPayDetail orderPayDetail) {
        PayResponseStatus payResponseStatus = payNotifyResult.getPayResponse().getPayResponseStatus();

        if (Paying.equals(payResponseStatus)) {
            orderPayDetailService.updatePayResponse(payNotifyResult);
        } else {
            boolean succeed = Succeed.equals(payResponseStatus);

            // 如果在线支付的金额和支付金额不一样说明有线下支付
            if (!Objects.equals(orderPay.getTotalPayAmount(), orderPayDetail.getPayAmount())) {
                List<OrderPayDetail> offlinePayDetails = orderPayDetailService.getOfflinePayByPayId(orderPay.getId());

                // 处理线下支付
                if (succeed) {
                    // 处理汇票
                    orderOfflinePayService.submitPay(offlinePayDetails);
                    // 处理支付明细
                    orderPayDetailService.submitOfflinePay(orderPay.getId());
                } else {
                    // 处理汇票
                    orderOfflinePayService.releaseOfflinePay(offlinePayDetails);
                    // 处理支付明细
                    orderPayDetailService.rollbackOfflinePay(orderPay.getId());
                }
            }

            if (succeed) {
                processOnlinePaySucceed(payNotifyResult, orderPay, orderPayDetail);

                // pay主数据状态更新
                orderPayService.submitPay(orderPay.getId());

                // pay明细数据状态更新
                orderPayDetailService.submitOnlinePay(orderPayDetail, payNotifyResult);

            } else {
                processOnlinePayFailed(payNotifyResult, orderPay, orderPayDetail);

                // pay主数据状态更新
                orderPayService.rollbackPay(orderPay.getId());

                // pay明细数据状态更新
                orderPayDetailService.rollbackOnlinePay(orderPayDetail, payNotifyResult);
            }
        }
    }

    /**
     * 检查订单
     */
    protected Order checkOrder(String orderId) {
        Order order = orderService.getById(orderId);
        if (order == null) {
            throw new BytzBootException("找不到订单");
        }

        if (!getCanPayOrderStatus().contains(order.getStatus())) {
            throw new BytzBootException("当前状态不可支付预付款");
        }

        if (orderPayService.hasPayingRecord(order.getId())) {
            throw new BytzBootException("该订单当前有支付中记录，暂不能创建新的支付记录");
        }

        return order;
    }

    /**
     * 检查线下付凭证
     */
    protected List<OrderOfflinePay> checkOfflinePay(List<OfflinePayIn> offlinePaysIn) {
        if (CollectionUtil.isNotEmpty(offlinePaysIn)) {
            // 去数据库中查出所有凭证
            Set<String> offlinePayIds = offlinePaysIn.stream()
                    .map(OfflinePayIn::getId).collect(Collectors.toSet());

            Map<String, OrderOfflinePay> offlinePayMap = orderOfflinePayService.getByIds(offlinePayIds)
                    .stream().collect(Collectors.toMap(OrderOfflinePay::getId, v -> v));

            // 保证输入顺序不变
            List<OrderOfflinePay> result = new ArrayList<>(offlinePaysIn.size());
            for (OfflinePayIn offlinePay : offlinePaysIn) {
                OrderOfflinePay orderOfflinePay = offlinePayMap.get(offlinePay.getId());
                if (orderOfflinePay == null) {
                    throw new BytzBootException("未找到支付凭证【" + offlinePay.getRemitName() + "】");
                } else {
                    result.add(orderOfflinePay);
                }
            }

            // 验证
            orderOfflinePayService.checkCanPay(result);

            return result;
        }

        return null;
    }


    /**
     * 线上付
     * @return 是否需要线上付
     */
    protected OrderPayDetail payByOnline(PIN payIn, PC context, BigDecimal remainAmount) {
        Order order = context.getOrder();
        List<OrderPayDetail> orderPayDetails = context.getOrderPayDetails();
        List<OrderItem> orderItems = context.getOrderItems();

        if (remainAmount.compareTo(BigDecimal.ZERO) > 0) {
            OrderPayDetail orderPayDetail = new OrderPayDetail();
            orderPayDetails.add(orderPayDetail);

            orderPayDetail.setPayWay(OrderPayConstants.PayWay.CHINA_PAY);
            orderPayDetail.setPayAmount(remainAmount);
            orderPayDetail.setStatus(OrderPayConstants.PayStatus.PAYING);
            orderPayDetail.setTranDate(DateUtil.format(LocalDateTime.now(), PURE_DATE_PATTERN));
            LocalDateTime expirationTime = LocalDateTime.now().plusMinutes(configService.getGlobal().getPayTimeOut());
            orderPayDetail.setExpirationTime(expirationTime);

            String commodityMsg = calculateCommodityMessage(payIn, order, orderItems);
            orderPayDetail.setCommodityMsg(commodityMsg);

            return orderPayDetail;
        }

        return null;
    }

    /**
     * 通过线下付结算
     */
    protected BigDecimal payByOffline(PC context) {
        List<OrderOfflinePay> offlinePays = context.getOfflinePays();
        BigDecimal prepayAmount = context.getPayAmount();
        List<OrderPayDetail> orderPayDetails = context.getOrderPayDetails();

        BigDecimal remainAmount = prepayAmount;
        // 扣线下付凭证
        if (offlinePays != null) {
            for (OrderOfflinePay offlinePay : offlinePays) {
                BigDecimal offlineCanPayAmount = offlinePay.getRemainAmount();

                // 创建支付明细数据
                OrderPayDetail orderPayDetail = new OrderPayDetail();
                orderPayDetails.add(orderPayDetail);

                orderPayDetail.setPayWay(OrderPayConstants.PayWay.OFFLINE);
                orderPayDetail.setOfflinePayId(offlinePay.getId());
                orderPayDetail.setTransactionNumber(offlinePay.getRemitNumber());
                orderPayDetail.setStatus(OrderPayConstants.PayStatus.PAYING);
                LocalDateTime expirationTime = LocalDateTime.now().plusMinutes(configService.getGlobal().getPayTimeOut());
                orderPayDetail.setExpirationTime(expirationTime);

                if (remainAmount.compareTo(offlineCanPayAmount) > 0) {
                    // 如果剩余要付的金额 > 可付额 then 把所有额都付了
                    orderPayDetail.setPayAmount(offlineCanPayAmount);
                    remainAmount = remainAmount.subtract(offlineCanPayAmount);
                } else {
                    // 如果剩余要付的金额 <= 可付额 then 只把剩余的付了
                    orderPayDetail.setPayAmount(remainAmount);
                    remainAmount = BigDecimal.ZERO;

                    // 然后就结束了
                    break;
                }
            }
        }
        return remainAmount;
    }
}
